package com.gmrz.uaf.crypto.sm;

import com.gmrz.util.Convert;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.cms.*;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.util.Store;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;

public class SM2Utils {
    private static final Logger LOG = LogManager.getLogger(SM2Utils.class);
//	public static byte[] encrypt(byte[] publicKey, byte[] data)
//	{
//		if (publicKey == null || publicKey.length == 0)
//		{
//			return null;
//		}
//
//		if (data == null || data.length == 0)
//		{
//			return null;
//		}
//
//		byte[] source = new byte[data.length];
//		System.arraycopy(data, 0, source, 0, data.length);
//
//		byte[] formatedPubKey;
//		if (publicKey.length == 64){
//			//添加一字节标识，用于ECPoint解析
//			formatedPubKey = new byte[65];
//			formatedPubKey[0] = 0x04;
//			System.arraycopy(publicKey,0,formatedPubKey,1,publicKey.length);
//		}
//		else
//			formatedPubKey = publicKey;
//
//		Cipher cipher = new Cipher();
//		SM2 sm2 = SM2.Instance();
//		ECPoint userKey = sm2.ecc_curve.decodePoint(formatedPubKey);
//
//		ECPoint c1 = cipher.Init_enc(sm2, userKey);
//		cipher.Encrypt(source);
//		byte[] c3 = new byte[32];
//		cipher.Dofinal(c3);
//
//		DERInteger x = new DERInteger(c1.getX().toBigInteger());
//		DERInteger y = new DERInteger(c1.getY().toBigInteger());
//		DEROctetString derDig = new DEROctetString(c3);
//		DEROctetString derEnc = new DEROctetString(source);
//		ASN1EncodableVector v = new ASN1EncodableVector();
//		v.add(x);
//		v.add(y);
//		v.add(derDig);
//		v.add(derEnc);
//		DERSequence seq = new DERSequence(v);
//		ByteArrayOutputStream bos = new ByteArrayOutputStream();
//		DEROutputStream dos = new DEROutputStream(bos);
//		try {
//			dos.writeObject(seq);
//			return bos.toByteArray();
//		} catch (IOException e) {
//			e.printStackTrace();
//			return null;
//		}
//	}


//	public static byte[] decrypt(byte[] privateKey, byte[] encryptedData)
//	{
//		if (privateKey == null || privateKey.length == 0)
//		{
//			return null;
//		}
//
//		if (encryptedData == null || encryptedData.length == 0)
//		{
//			return null;
//		}
//
//		byte[] enc = new byte[encryptedData.length];
//		System.arraycopy(encryptedData, 0, enc, 0, encryptedData.length);
//
//		SM2 sm2 = SM2.Instance();
//		BigInteger userD = new BigInteger(1, privateKey);
//
//		ByteArrayInputStream bis = new ByteArrayInputStream(enc);
//		ASN1InputStream dis = new ASN1InputStream(bis);
//		try {
//			DERObject derObj = dis.readObject();
//			ASN1Sequence asn1 = (ASN1Sequence) derObj;
//			DERInteger x = (DERInteger) asn1.getObjectAt(0);
//			DERInteger y = (DERInteger) asn1.getObjectAt(1);
//			ECPoint c1 = sm2.ecc_curve.createPoint(x.getValue(), y.getValue(), true);
//
//			Cipher cipher = new Cipher();
//			cipher.Init_dec(userD, c1);
//			DEROctetString data = (DEROctetString) asn1.getObjectAt(3);
//			enc = data.getOctets();
//			cipher.Decrypt(enc);
//			byte[] c3 = new byte[32];
//			cipher.Dofinal(c3);
//			return enc;
//		} catch (IOException e) {
//			e.printStackTrace();
//			return null;
//		}
//	}

//	public static byte[] sign(byte[] userId, byte[] privateKey, byte[] sourceData)
//	{
//		if (privateKey == null || privateKey.length == 0) {
//			return null;
//		}
//
//		if (sourceData == null || sourceData.length == 0)
//		{
//			return null;
//		}
//
//		SM2 sm2 = SM2.Instance();
//		BigInteger userD = new BigInteger(privateKey);
////		LOG.debug("userD: " + userD.toString(16));
//
//		ECPoint userKey = sm2.ecc_point_g.multiply(userD);
////		LOG.debug("椭圆曲线点X: " + userKey.getX().toBigInteger().toString(16));
////		LOG.debug("椭圆曲线点Y: " + userKey.getY().toBigInteger().toString(16));
//
//		SM3Digest sm3 = new SM3Digest();
//		byte[] z = sm2.sm2GetZ(userId, userKey);
////		LOG.debug("SM3摘要Z: " + Util.getHexString(z));
////		LOG.debug("M: " + Util.getHexString(sourceData));
//
//		sm3.update(z, 0, z.length);
//		sm3.update(sourceData, 0, sourceData.length);
//		byte[] md = new byte[32];
//		sm3.doFinal(md, 0);
//
////		LOG.debug("SM3摘要值: " + Util.getHexString(md));
//
//		SM2Result sm2Result = new SM2Result();
//		sm2.sm2Sign(md, userD, userKey, sm2Result);
////		LOG.debug("r: " + sm2Result.r.toString(16));
////		LOG.debug("s: " + sm2Result.s.toString(16));
//
//		DERInteger d_r = new DERInteger(sm2Result.r);
//		DERInteger d_s = new DERInteger(sm2Result.s);
//		ASN1EncodableVector v2 = new ASN1EncodableVector();
//		v2.add(d_r);
//		v2.add(d_s);
//		DERObject sign = new DERSequence(v2);
//		return sign.getDEREncoded();
//	}


    public static boolean verifySign(byte[] publicKey, byte[] sourceData, byte[] signData) {
        byte[] userid = new byte[]{0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38};
        String publicKeyStr = Convert.toBase64(publicKey);
        String sourceDatayStr = Convert.toBase64(sourceData);
        String signDataStr = Convert.toBase64(signData);
        LOG.debug("verifySign publicKeyStr:" + publicKeyStr);
        LOG.debug("verifySign sourceDatayStr:" + sourceDatayStr);
        LOG.debug("verifySign signDataStr:" + signDataStr);
        if (publicKey == null || publicKey.length == 0) {
            return false;
        }
        if (sourceData == null || sourceData.length == 0) {
            return false;
        }
        byte[] formatedPubKey;
        if (publicKey.length == 64) {
            formatedPubKey = new byte[65];
            formatedPubKey[0] = 0x04;
            System.arraycopy(publicKey, 0, formatedPubKey, 1, publicKey.length);
        } else {
            formatedPubKey = publicKey;
        }
        SM2 sm2 = SM2.Instance();
        ECPoint userKey = sm2.ecc_curve.decodePoint(formatedPubKey);

        SM3Digest sm3 = new SM3Digest();
        byte[] z = sm2.sm2GetZ(userid, userKey);

//		LOG.debug("SM3摘要Z: " + Util.getHexString(z));
//		LOG.debug("M: " + Util.getHexString(sourceData));

        sm3.update(z, 0, z.length);
        sm3.update(sourceData, 0, sourceData.length);
        byte[] md = new byte[32];
        sm3.doFinal(md, 0);
//		LOG.debug("SM3摘要值: " + Util.getHexString(md));

        ByteArrayInputStream bis = new ByteArrayInputStream(signData);
        ASN1InputStream dis = new ASN1InputStream(bis);
        SM2Result sm2Result = null;
        try {
//			DERObject derObj = dis.readObject();
//			Enumeration<DERInteger> e = ((ASN1Sequence) derObj).getObjects();
//			BigInteger r = ((DERInteger)e.nextElement()).getValue();
//			BigInteger s = ((DERInteger)e.nextElement()).getValue();
            ASN1Encodable asn1Encodable = dis.readObject();
            ASN1Primitive asn1Primitive = asn1Encodable.toASN1Primitive();
            ASN1Sequence asn1 = (ASN1Sequence) asn1Primitive;
            Enumeration<ASN1Integer> e = asn1.getObjects();
            BigInteger r = e.nextElement().getValue();
            BigInteger s = e.nextElement().getValue();
            sm2Result = new SM2Result();
            sm2Result.r = r;
            sm2Result.s = s;
//			LOG.debug("r: " + sm2Result.r.toString(16));
//			LOG.debug("s: " + sm2Result.s.toString(16));
            sm2.sm2Verify(md, userKey, sm2Result.r, sm2Result.s, sm2Result);
            return sm2Result.r.equals(sm2Result.R);
        } catch (IOException e1) {
            e1.printStackTrace();
            return false;
        }
    }

    public static boolean verifySignRaw(byte[] publicKey, byte[] sourceData, byte[] signData) {
        byte[] userid = new byte[]{0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38};
        String publicKeyStr = Convert.toBase64(publicKey);
        String sourceDatayStr = Convert.toBase64(sourceData);
        String signDataStr = Convert.toBase64(signData);
        LOG.debug("verifySignRaw publicKeyStr:" + publicKeyStr);
        LOG.debug("verifySignRaw sourceDatayStr:" + sourceDatayStr);
        LOG.debug("verifySignRaw signDataStr:" + signDataStr);
        if (publicKey == null || publicKey.length == 0) {
            return false;
        }
        if (sourceData == null || sourceData.length == 0) {
            return false;
        }
        // 根据公钥的长度判断是否对其进行格式化
        byte[] formatedPubKey;
        if (publicKey.length == 64) {
            formatedPubKey = new byte[65];
            formatedPubKey[0] = 0x04;
            System.arraycopy(publicKey, 0, formatedPubKey, 1, publicKey.length);
        } else {
            formatedPubKey = publicKey;
        }
        SM2 sm2 = SM2.Instance();
        // 将公钥解码为 EC 椭圆曲线上的点
        ECPoint userKey = sm2.ecc_curve.decodePoint(formatedPubKey);

        SM3Digest sm3 = new SM3Digest();
        byte[] z = sm2.sm2GetZ(userid, userKey);

//		LOG.debug("SM3摘要Z: " + Util.getHexString(z));
//		LOG.debug("M: " + Util.getHexString(sourceData));

        sm3.update(z, 0, z.length);
        sm3.update(sourceData, 0, sourceData.length);
        byte[] md = new byte[32];
        sm3.doFinal(md, 0);
//		LOG.debug("SM3摘要值: " + Util.getHexString(md));

        SM2Result sm2Result = null;
        if (signData != null && signData.length == 64) {
            sm2Result = new SM2Result();
            byte[] r = new byte[32];
            System.arraycopy(signData, 0, r, 0, 32);
            sm2Result.r = new BigInteger(1, r);
            byte[] s = new byte[32];
            System.arraycopy(signData, 32, s, 0, 32);
            sm2Result.s = new BigInteger(1, s);
            sm2.sm2Verify(md, userKey, sm2Result.r, sm2Result.s, sm2Result);
            boolean f = sm2Result.r.equals(sm2Result.R);
            if(!f){
                LOG.error("sm2Result.r:"+sm2Result.r+",sm2Result.R:"+sm2Result.R);
            }
            return f;
        }
        return false;
    }

    public static boolean verifySecondLevelCertSign(byte[] publicKey, byte[] rootPublicKey, byte[] sourceData, byte[] signData) {
        String userId = "1234567812345678";
        if (publicKey == null || publicKey.length == 0) {
            return false;
        }
        if (sourceData == null || sourceData.length == 0) {
            return false;
        }
        byte[] formatedPubKey;
        if (publicKey.length == 64) {
            formatedPubKey = new byte[65];
            formatedPubKey[0] = 0x04;
            System.arraycopy(publicKey, 0, formatedPubKey, 1, publicKey.length);
        } else {
            formatedPubKey = publicKey;
        }
        SM2 sm2 = SM2.Instance();
        ECPoint userKey = sm2.ecc_curve.decodePoint(formatedPubKey);
        SM3Digest sm3 = new SM3Digest();
        byte[] z = sm2.sm2GetZ(userId.getBytes(), userKey);
//		LOG.debug("SM3摘要Z: " + Util.getHexString(z));
//		LOG.debug("M: " + Util.getHexString(sourceData));
        sm3.update(z, 0, z.length);
        sm3.update(sourceData, 0, sourceData.length);
        byte[] md = new byte[32];
        sm3.doFinal(md, 0);
//		LOG.debug("SM3摘要值: " + Util.getHexString(md));

        byte[] formatedRootPublicKey;
        if (rootPublicKey.length == 64) {
            //添加一字节标识，用于ECPoint解析
            formatedRootPublicKey = new byte[65];
            formatedRootPublicKey[0] = 0x04;
            System.arraycopy(rootPublicKey, 0, formatedRootPublicKey, 1, rootPublicKey.length);
        } else {
            formatedRootPublicKey = rootPublicKey;
        }
        SM2 sm2B = SM2.Instance();
        ECPoint rootPublicKeyP = sm2B.ecc_curve.decodePoint(formatedRootPublicKey);

        ByteArrayInputStream bis = new ByteArrayInputStream(signData);
        ASN1InputStream dis = new ASN1InputStream(bis);
        SM2Result sm2Result = null;
        try {
            ASN1Encodable asn1Encodable = dis.readObject();
            ASN1Primitive asn1Primitive = asn1Encodable.toASN1Primitive();
            ASN1Sequence asn1 = (ASN1Sequence) asn1Primitive;
            Enumeration<ASN1Integer> e = asn1.getObjects();
            BigInteger r = e.nextElement().getValue();
            BigInteger s = e.nextElement().getValue();
            sm2Result = new SM2Result();
            sm2Result.r = r;
            sm2Result.s = s;
            sm2.sm2Verify(md, rootPublicKeyP, sm2Result.r, sm2Result.s, sm2Result);
            return sm2Result.r.equals(sm2Result.R);
        } catch (IOException e1) {
            e1.printStackTrace();
            return false;
        }
    }

    public static Sm2KeyPair generateKeyPair() {
        SM2 sm2 = SM2.Instance();
        AsymmetricCipherKeyPair keypair = sm2.ecc_key_pair_generator.generateKeyPair();
        ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters) keypair.getPrivate();
        ECPublicKeyParameters ecpub = (ECPublicKeyParameters) keypair.getPublic();

        byte[] priKey = new byte[32];
        byte[] pubKey = new byte[64];

        byte[] bigNumArray = ecpriv.getD().toByteArray();
        System.arraycopy(bigNumArray, bigNumArray[0] == 0 ? 1 : 0, priKey, 0, 32);
        System.arraycopy(ecpub.getQ().getEncoded(), 1, pubKey, 0, 64);

        return new Sm2KeyPair(priKey, pubKey);
    }

    public static byte[] getData(byte[] p7Data) throws Exception {
            CMSSignedData sig = new CMSSignedData(p7Data);
            Store certs = sig.getCertificates();
            SignerInformationStore signers = sig.getSignerInfos();
            Collection<?> c = signers.getSigners();
            Iterator<?> it = c.iterator();
            SignerInformation signer;
            Collection<?> certCollection;
            Iterator<?> certIt;
            CMSTypedData contentInfo = sig.getSignedContent();
            byte[] data = (byte[]) contentInfo.getContent();
            return data;
    }

    public static Certificate getCert(byte[] certBytes) throws Exception{
        InputStream inStream = new ByteArrayInputStream(certBytes);
        ASN1Sequence seq = null;
        ASN1InputStream aIn;
        byte[] pk = null;
        aIn = new ASN1InputStream(inStream);
        seq = (ASN1Sequence)aIn.readObject();
        org.bouncycastle.asn1.x509.Certificate cert = org.bouncycastle.asn1.x509.Certificate.getInstance(seq);
        CertificateFactory cf = new CertificateFactory();
        InputStream is1 = new ByteArrayInputStream(cert.getEncoded());
        Certificate theCert = (Certificate) cf.engineGenerateCertificate(is1);
        is1.close();
        PublicKey publicKey = theCert.getPublicKey();
        return theCert;
    }
    public static byte[] getDN(byte[] p10Data) throws NoSuchAlgorithmException, SignatureException, IOException {
        PKCS10CertificationRequest pkcs10CertificationRequest = new PKCS10CertificationRequest(p10Data);
        X500Name x500Name = pkcs10CertificationRequest.getSubject();
        return x500Name.toString().getBytes();
    }
    public static void main(String[] args) throws NoSuchAlgorithmException, SignatureException, IOException {
//		byte[] publicKey = Convert.fromBase64("1mQ3OEyzflkQ4PJhlSQ2UgSxuv17ZAjs0HfyuyZmq9BA_4eB1t0m39BcBzjPGBjglA3_YnibFd460yvmWQ8ejg");
//	 byte[] sourceData = Convert.fromBase64("MIIBWqADAgECAgEBMBQGCCqBHM9VAYN1BggqgRzPVQGDdTBBMQswCQYDVQQGEwJDTjEPMA0GA1UEChMGTEVOT1ZPMQ8wDQYDVQQDEwZST09UQ0ExEDAOBgNVBAcTB0JFSUpJTkcwHhcNMTcwNjA4MTMwNjA5WhcNMjIwNjA3MTMwNjA5WjBBMQswCQYDVQQGEwJDTjEPMA0GA1UEChMGTEVOT1ZPMQ8wDQYDVQQDEwZST09UQ0ExEDAOBgNVBAcTB0JFSUpJTkcwWTATBgcqhkjOPQIBBggqgRzPVQGCLQNCAATWZDc4TLN-WRDg8mGVJDZSBLG6_XtkCOzQd_K7Jmar0ED_h4HW3Sbf0FwHOM8YGOCUDf9ieJsV3jrTK-ZZDx6OozkwNzAJBgNVHRMEAjAAMAsGA1UdDwQEAwID6DAdBgNVHQ4EFgQU2jmj7l5rSw0yVb_vlWAYkK_YBwk");
//	 byte[] signData = Convert.fromBase64("MEQCIHSfpzKt4MGtVXJkvWHW_OVM7fgB5IdmlRmbInAOzKTdAiBfYoATXiNc4Xf3C05rM-5-InaBNRG0RBrfbD5V1KfjPw");

//	 boolean f  = verifySign(publicKey,sourceData,signData);
//	 byte[] publicKey = Convert.fromBase64("PeULAt925tWHajTHvfdXSPP2r8ZjMJeN_FNZTchfEX2ip6oyh59zcjaRUgInPGXRSUQMW6IZHH8XflyPGvTorQ");
//	 byte[] sourceData = Convert.fromBase64("AlXRAAZVIAC_-yaDl_7fpWj3y9I0eSTwonBc-e46KAgHHhpsyHtXeANSIADp7KC6J2hEz4OPtrsAOoGBG8595Te13N0-j5mc-EXdVxVSIACocXVytpw0G248tdaa-4aIqXAuCNj1SrPDTmzkqKbFyBZSAAAFVQQAAQABAAtSCQAwMDRBI0ZGRjMXUkAAPeULAt925tWHajTHvfdXSPP2r8ZjMJeN_FNZTchfEX2ip6oyh59zcjaRUgInPGXRSUQMW6IZHH8XflyPGvTorQ1SBAA4MTI3");
//	 byte[] signData = Convert.fromBase64("JZdGmEW5uVbd9po1N7euA-bWZlquRK4IYxsZp5wZROAxv6k-pco1GIjUpxVjttSy7iMzTvvZHHvZ1WOas0JnSA");
//
//
//	 boolean f  = verifySignRaw(publicKey,sourceData,signData);
//        Sm2KeyPair keyPair =  generateKeyPair();
//        System.out.println("k:"+keyPair.getPriKey());
//        String p10 ="MIIBFTCBvAIBADAxMQ0wCwYDVQQDDARHTVJaMQ8wDQYDVQQLDAYxMjM0NTYxDzANBgNVBC0MBjEyMzQ1NjBQMAoGCCqBHM9VAYN1A0IABHCy5lqr22MbXX8H125FX2Apf_u0ygGhULIQrLzJ62hAtS9N3ss7jklxVswBDEb6WNxpOe3qRfE7BR5LMVN8asugMjAwBgkqhkiG9w0BCQ4xIzAhMA8GA1UdEwEB_wQFMAMBAf8wDgYDVR0PAQH_BAQDAgIEMAoGCCqBHM9VAYN1A0gAMEUCIQCmUPvti9o8eJfO4tlYlhcJwXPS0uKwAXPKCCvaJ1uEugIgXV79gfoFqNzQY0kERBwZZPN3x3DaI4dvQjkRDNyNeTA";
//        getDN(Convert.fromBase64(p10));

        //byte[] publicKey = Convert.fromBase64("AuNhHfbzuMlIWslQxc7Shw/tWUIqcwzATU+J8oTB29V7ZGZB74D48Q8YXhMCltL659OzB8aY4emdy7btve7XwA==");
        //byte[] sourceData = "测试123456test".getBytes("utf-8");
	    //byte[] signData = Convert.fromBase64("MEYCIQC344AmCL8YrpXlodV+3/vJScivFUdk8ZMLVgHjn6zlKwIhAODT25cosuqwBlczaup8dGKFf19ufOF4pv6625Wjdxiu");
        byte[] publicKey = Convert.fromBase64("BNagNLxWXmJjH-AtXJn2-x0MjSD6L7BBoImyEACt4nD08o0bRE1aCuRPv4NVVAD11zHh4sqqNeYbKrBrn_UNt5k");
        byte[] sourceData = Convert.fromBase64("Az7VAAsuCQAwMDRBIzAxQUUOLgcAAAEBBwAAAQouIADGYmviMMPbSCTS33x9oJyF0cCdlx6SXZ0yxQIptmg8IAkuIAA0NxPHtr4U5dVJ2kZixrzKPN8oRBuW5P-cUaKFcu94OQ0uCAAIAAAACAAAAAwuQQAE1qA0vFZeYmMf4C1cmfb7HQyNIPovsEGgibIQAK3icPTyjRtETVoK5E-_g1VUAPXXMeHiyqo15hsqsGuf9Q23mQQBIAAxMjM0NTY3ODEyMzQ1Njc4MTIzNDU2NzgxMjM0NTY3OA");
        byte[] signData = Convert.fromBase64("F87i1yh_JMdJoL-4tbi5uWMse2jk-Tfpy_naEq0x3CMDKvRw-UsoUQ5YsLesQw3yyaoHl2Pq4NHI3M66JCUUzQ");
        boolean f  = verifySignRaw(publicKey,sourceData,signData);
	    System.out.println("f:"+f);
    }


}
